<?php

namespace app\api\service;

use app\common\model\Alipay;
use app\common\model\Wxpay;
use app\model\Agent;
use app\model\AgentBalanceLog;
use app\model\AgentChannel;
use app\model\AgentServiceConfig;
use app\model\BaseSupplier;
use app\model\OilCardOrder;
use app\model\OrderWkyOilCard;
use app\model\OrderWkyPower;
use app\model\PowerOrder;
use think\facade\Db;
use think\facade\Request;

/*
支付相关
各方法中的
*/
class OrderService extends CommonService
{
    public $orderModelName;
    public $orderGoodsModelName;
    public $servType;

    public function __construct($servType)
    {
        $this->servType = $servType;
        switch ($servType) {
            case PRODUCT_OILCARD_SERV:
                $this->orderGoodsModelName = OrderWkyOilCard::class;
                $this->orderModelName = OilCardOrder::class;
            break;
            case PRODUCT_POWER_SERV:
                $this->orderGoodsModelName = OrderWkyPower::class;
                $this->orderModelName = PowerOrder::class;
            break;
        }
    }

    /*
     预支付 ，由于商品不同，价格校验不在此处
     priceInfo ['origin_amount','amount']  单位元
     supplier 供应商信息，如果是充值类型 的需要关with联 charge_supplier_config
     channelInfo  渠道信息。该参数为id 或者根据id查询的Collection
     orderGoods 不同类型订单 商品信息 ，需要根据业务类型自行处理
     userInfo 来封装登录认证中间件中的用户信息，如果不是中间件参考之
     payType 支付方式
     servType 业务类型
     apiParam 第三方支付需要的参数
    */
    public function prepay(array $priceInfo, $supplier, $channelInfo, array $orderGoods, $type = 0, array $userInfo, $payType, $apiParam = null)
    {
        // step 根据渠道id获取 渠道 代理商信息，以及代理商开通的业务
        if (is_numeric($channelInfo)) { // 数字类型标识id
            $channelInfo = AgentChannel::find($channelInfo);
        }
        if (!$channelInfo) {
            return $this->inValiReturn('该渠道已下线');
        }
        $agentInfo = Agent::find($channelInfo->agent_id);
        if (!$agentInfo) {
            return $this->inValiReturn('该渠道已下线');
        }
        if ('1' != $agentInfo['status']) {
            return $this->inValiReturn('该渠道已下线');
        }
        // 代理商业务
        $agentServiceConfig = AgentServiceConfig::where('agent_id', $channelInfo->agent_id)
                        ->where('serv_type', $this->servType)
                        ->where('status', 1)
                        ->find();
        if (!$agentServiceConfig) {
            return $this->inValiReturn((PRODUCT_SERV_TYPE[$this->servType]['name']).'业务已经关闭');
        }

        // todo 代理商余额处理判断
        if (SETTLE_TYPE_PREPAY == $agentInfo->settle_typle) {
            if ($agentInfo->balance < $priceInfo['amount']) {
                return $this->inValiReturn('下单失败，代理商余额不足，请联系我们或者稍后重试');
            }
        }

        // 判断接口有效期
        if (in_array($this->servType, [PRODUCT_MEAL_SERV, PRODUCT_METUAN_SERV])) {
            if (!($agentServiceConfig->time_end > time()) && $agentServiceConfig->income_type == AgentServiceConfig::INCOME_TYPE_SELF) {
                return $this->inValiReturn('下单失败，代理商服务已到期，请联系我们');
            }
        }

        $prepayData = [];
        $orderSn = getOrderSn();

        // 价格 由元 转换 分
        foreach ($priceInfo as $k => $v) {
            $priceInfo[$k] = yuan2fen($v);
        }

        try {
            // step 做预支付
            if (PAY_WEIXIN == $payType) { // 微信支付
                if ('H5' == Request::header('browser_type')) {
                    $pay = new Wxpay($agentInfo->id, 'wechat_mweb');
                    $prepayData = $pay->create([
                        'body' => PRODUCT_SERV_TYPE[$this->servType]['name'].'充值 '.fen2yuan($priceInfo['origin_amount']).' 元',
                        'out_trade_no' => $orderSn,
                        'total_fee' => $priceInfo['amount'],
                        'spbill_create_ip' => $apiParam['ip'] ?? Request::ip(),
                        'trade_type' => Request::header('browser_type') ?? '',
                    ]);
                    if (!isset($prepayData['return_code']) || 'SUCCESS' != $prepayData['return_code']) {
                        return $this->inValiReturn($prepayData['return_msg'] ?? '订单生成失败，请稍后再试');
                    }
                    if (!empty($prepayData['mweb_url'])) {
                        $prepayData['mweb_url'] .= '&redirect_url='.urlencode($apiParam['redirect_uri'] ?? 'todo 跳转url');
                    }
                } else {
                    $pay = new Wxpay($agentInfo->id);
                    $prepayData = $pay->create([
                        'body' => PRODUCT_SERV_TYPE[$this->servType]['name'].'充值 '.fen2yuan($priceInfo['origin_amount']).' 元',
                        'out_trade_no' => $orderSn,
                        'total_fee' => $priceInfo['amount'],
                        'spbill_create_ip' => $this->request->ip(),
                        'trade_type' => 'jsapi',
                        'openid' => $userInfo['platform']['openid'],
                    ]);
                    if (!isset($prepayData['return_code']) || 'SUCCESS' != $prepayData['return_code']) {
                        return $this->inValiReturn($prepayData['return_msg'] ?? '订单生成失败，请稍后再试');

                        return fail($res['return_msg'] ?? '订单生成失败，请稍后再试');
                    }
                    $prepayData = $pay->jssdk($prepayData['prepay_id']);
                    $prepayData['order_sn'] = $orderSn;
                }
            } elseif (PAY_ALIPAY == $payType) {
                $pay = new Alipay($agentInfo->id);
                $prepayData = $pay->wap([
                    'name' => PRODUCT_SERV_TYPE[$this->servType]['name'].'充值 '.fen2yuan($priceInfo['origin_amount']).' 元',
                    'out_trade_no' => $orderSn,
                    'total_amount' => $priceInfo['amount'],
                    'channel' => $channelInfo->id,
                ]);
            } else {
                return $this->inValiReturn('支付方式不支持');
            }
        } catch (\Exception $e) {
            doWarning('发起支付失败'.getExceptionMsg($e));

            return $this->inValiReturn('支付失败，'.getExceptionMsg($e));
        }

        // 处理订单
        Db::startTrans();
        try {
            $orderModel = new $this->orderModelName();
            $orderModel->supplier_id = $supplier['id'];
            $orderModel->agent_id = $agentInfo['id'];
            $orderModel->sn = $orderSn;
            $orderModel->user_id = $userInfo['id'];
            $orderModel->origin_amount = $priceInfo['origin_amount'];
            $orderModel->amount = $priceInfo['amount'];
            $orderModel->pay_amount = $priceInfo['amount'];
            $orderModel->pay_type = $payType;
            $orderModel->serv_type = $this->servType;
            $orderModel->cnid = $channelInfo->id;
            $orderModel->type = $type;

            // 计算代理商结算价格相关
            $orderModel->agent_settle_type = $agentInfo->settle_type;
            $orderModel->agent_settle_value = $agentServiceConfig->value;
            $orderModel->agent_profit = calcAgentProfit($agentInfo->settle_type, $agentServiceConfig->value, $orderModel['origin_amount'], $orderModel['amount']);

            // 计算供应商结算价格相关
            $orderModel->supplier_settle_type = $supplier->settle_type;
            if (SLOW_RECHARGE == $orderGoods['recharge_type']) {
                if ($supplier->rechargeConfig->state) {
                    return $this->inValiReturn('尚未开启慢充');
                }
                $orderModel->supplier_settle_value = $supplier->rechargeConfig->slow_settle_value;
            } elseif (FAST_RECHARGE == $orderGoods['recharge_type']) {
                if ($supplier->rechargeConfig->state) {
                    return $this->inValiReturn('尚未开启快充');
                }
                $orderModel->supplier_settle_value = $supplier->rechargeConfig->fast_settle_value;
            } else {
                return $this->inValiReturn('未知充值类型');
            }

            $orderModel->supplier_gate_rate = $supplier->gate_rate;
            $supplierGateAmount = supplierGateAmount($orderModel->pay_amount, $orderModel->supplier_gate_rate);
            $supplierProfit = calcAgentProfit($orderModel->supplier_settle_type, $orderModel->supplier_settle_value, $orderModel->origin_amount, $orderModel->amount);
            $orderModel->profit = calcProfit($supplierProfit, $orderModel->agent_profit, $supplierGateAmount);

            if (!$orderModel->save()) {
                Db::rollback();

                return $this->inValiReturn('下单失败，我们出错了，请重试或者联系我们');
            }
            // 保存商品
            if ($this->orderGoodsModelName) {
                $orderGoodsModel = new $this->orderGoodsModelName();
                foreach ($orderGoods as $k => $v) {
                    $orderGoodsModel->{$k} = $v;
                }
                $orderGoodsModel->order_id = $orderModel->id;
                if (!$orderGoodsModel->save()) {
                    Db::rollback();

                    return $this->inValiReturn('下单失败，我们出错了，请重试或者联系我们');
                }
            }
            Db::rollback();

            return $this->valiReturn($prepayData);
        } catch (\Exception $e) {
            Db::rollback();
            doWarning('预下单入库事务报错'.getExceptionMsg($e));

            return $this->valiReturn('下单失败，请重试或者联系我们');
        }
    }

    /*
    非渠道下的后台添加，免支付
    priceInfo ['origin_amount','amount']  单位元
    supplier 供应商信息，如果是充值类型 的需要关with联 charge_supplier_config
    channelInfo  渠道信息。该参数为id 或者根据id查询的Collection
    orderGoods 不同类型订单 商品信息 ，需要根据业务类型自行处理
    userInfo 来封装登录认证中间件中的用户信息，如果不是中间件参考之
    payType 支付方式
    servType 业务类型
    apiParam 第三方支付需要的参数
    */
    public function adminPay(array $priceInfo, $supplier, $agentInfo, array $orderGoods, $type = 0, $agentService, $subfix = '')
    {
        $payType = 3;
        $return = [];
        $orderSn = getOrderSn('', $subfix);
        $orderModel = new $this->orderModelName();
        $orderModel->supplier_id = $supplier['id'];
        $orderModel->agent_id = $agentInfo['id'];
        $orderModel->sn = $orderSn;
        $orderModel->user_id = 0;
        $orderModel->status = 2; // 默认推送 供应商失败
        $orderModel->origin_amount = $priceInfo['origin_amount'];
        $orderModel->amount = intval($priceInfo['origin_amount'] * ($agentService->value) / 100);
        $orderModel->pay_amount = $orderModel->amount;
        $orderModel->pay_type = $payType;
        $orderModel->serv_type = $this->servType;
        $orderModel->cnid = 0;  // 代理商后台添加 没有渠道信息
        $orderModel->type = $type;
        // 计算代理商结算价格相关
        $orderModel->agent_settle_type = $agentInfo->settle_type;
        $orderModel->agent_settle_value = $agentService->value;
        $orderModel->agent_profit = calcAgentProfit($agentInfo->settle_type, $agentService->value, $orderModel->origin_amount, $orderModel->amount);

        // 计算供应商结算价格相关
        $orderModel->supplier_settle_type = $supplier->settle_type;
        if (SLOW_RECHARGE == $orderGoods['recharge_type']) {
            if ($supplier->rechargeConfig->state) {
                return $this->inValiReturn('尚未开启慢充');
            }
            $orderModel->supplier_settle_value = $supplier->rechargeConfig->slow_settle_value;
        } elseif (FAST_RECHARGE == $orderGoods['recharge_type']) {
            if ($supplier->rechargeConfig->state) {
                return $this->inValiReturn('尚未开启快充');
            }
            $orderModel->supplier_settle_value = $supplier->rechargeConfig->fast_settle_value;
        } else {
            return $this->inValiReturn('未知充值类型');
        }

        $orderModel->supplier_gate_rate = $supplier->gate_rate;
        $supplierGateAmount = supplierGateAmount($orderModel->pay_amount, $orderModel->supplier_gate_rate);
        $supplierProfit = calcAgentProfit($orderModel->supplier_settle_type, $orderModel->supplier_settle_value, $orderModel->origin_amount, $orderModel->amount);
        $orderModel->profit = calcProfit($supplierProfit, $orderModel->agent_profit, $supplierGateAmount);

        // 支付时间
        $orderModel->pay_status = 1;
        $orderModel->pay_time = date('Y-m-d H:i:s');
        if (!$orderModel->save()) {
            return $this->inValiReturn();
        }

        $return['order'] = $orderModel;
        // 保存商品
        if ($this->orderGoodsModelName) {
            $orderGoodsModel = new $this->orderGoodsModelName();
            foreach ($orderGoods as $k => $v) {
                $orderGoodsModel->{$k} = $v;
            }
            $orderGoodsModel->order_id = $orderModel->id;
            if (!$orderGoodsModel->save()) {
                return $this->inValiReturn();
            }
            $return['order_goods'] = $orderGoodsModel;
        }

        // 实时统计日报对账
        $daliyFinance = invoke(DailyFinance::class);
        $daliyFinance->increase($orderModel->agent_id, $orderModel->created_at, $orderModel->amount, $orderModel->agent_profit, $orderModel->profit);

        return $this->valiReturn($return);
    }

    /*
    订单支付成功
    参数 ：
        sn 订单号
        data 回调相关参数
    逻辑：
        1，更新订单状态
        2，扣除代理商余额 和供应商余额
    */
    public function orderSuccess($sn, $data)
    {
        Db::startTrans();
        try {
            // step 处理订单表相关
            $orderInfo = ($this->orderModelName)::where('sn', $sn)->where('pay_status', 0)->find();
            if (!$orderInfo) {
                Db::rollback();
                doLog('支付回调'.$sn.',订单不存在,或者已经处理过了');

                return $this->inValiReturn('订单不存在,或者已经处理过了');
            }
            $orderInfo->out_order_id = $data['trade_no'];
            $orderInfo->status = 1;
            $orderInfo->save();

            // step 代理商相关处理 预付类型扣除账户余额
            $res = $this->successAgentBalance($orderInfo);
            if ($res['code']) {
                Db::rollback();

                return $res;
            }

            // step 处理供应商处的余额
            $res = $this->successSupplierBalance($orderInfo);
            if ($res['code']) {
                Db::rollback();

                return $res;
            }

            // step 对账数据实时更新
            $res = (new DailyFinance())->increase($orderInfo->agent_id, $orderInfo->created_at, $orderInfo->amount, $orderInfo->agent_profit, $orderInfo->profit);

            if ($res['code']) {
                Db::rollback();

                return $res;
            }

            Db::commit();
            // todo 注：支付成功通知供应商 在各自业务里处理
            return $this->valiReturn(['order' => $orderInfo]);
        } catch (\Exception $e) {
            Db::rollback();
            doWarning(getExceptionMsg($e));

            return $this->inValiReturn('操作失败');
        }
    }

    /*
     订单完成后处理 代理商余额相关改变
    */
    public function successAgentBalance($orderInfo)
    {
        if (SETTLE_TYPE_PREPAY == $orderInfo->agent_settle_type) {
            $agentInfo = Agent::find($orderInfo->agent_id);
            if (!$agentInfo) {
                doWarning('支付回调'.$orderInfo->sn.',代理商不存在，程序仍继续执行');
            } else {
                $oldBalance = $agentInfo->balance;
                $res = Agent::where('id', $agentInfo->id)
                    ->where('balance', '>=', $orderInfo->amount)
                    ->dec('balance', $orderInfo->amount)
                    ->update();
                if (!$res) {
                    doWarning('支付回调'.$orderInfo->sn.',代理商扣款失败，请检查余额');

                    return $this->inValiReturn('回调失败，请联系我们');
                }
                // 入库balance
                $balanceModel = new AgentBalanceLog();
                $balanceLog = [
                    'agent_id' => $orderInfo->agent_id,
                    'type' => $balanceModel->servTypeOut($orderInfo->serv_type),
                    'amount' => ($orderInfo->amount),
                    'old_balance' => ($oldBalance),
                    'new_balance' => ($oldBalance - $orderInfo->amount),
                    'remark' => (PRODUCT_SERV_TYPE[$orderInfo->serv_type]['name']).'充值：'.fen2yuan($orderInfo->amount).'元，结算价：'.fen2yuan($orderInfo->amount).'元。订单号：'.$orderInfo->sn,
                    'quote_id' => $orderInfo->id,
                    'trade_no' => $orderInfo->sn,
                ];
                if (!$balanceModel->save($balanceLog)) {
                    doWarning('支付回调'.$orderInfo->sn.',代理商油卡充值扣费失败');

                    return $this->inValiReturn('回调失败，请联系我们');
                }
            }
        }

        return $this->valiReturn();
    }

    /*
     订单成功后 处理 平台在供应商处理的余额数据
    */
    public function successSupplierBalance($orderInfo)
    {
        // step 平台在供应商处余额处理
        if (SETTLE_TYPE_PREPAY === $orderInfo->supplier_settle_type) {
            $supplierAmount = intval($orderInfo->origin_amount * $orderInfo->supplier_settle_value / 100);
            $res = BaseSupplier::where('id', $orderInfo->supplier_id)->dec('balance', $supplierAmount)->update();
            if ($res) {
                doWarning('订单回调，处理平台在供应商处余额失败，为不影响流程，只是暂时记录日志，并让程序继续执行');
                // todo 此处只是更新我们在供应商的余额，即便失败也不做错误
            }
        }

        return $this->valiReturn();
    }

    /*
    退款
    不支持部分退款 和多次退款。部分退款需要传递退款金额 以及 修改订单显示，报表显示 以及一些收入计算逻辑
    param statusArr 存在的话，订单处于该状态的才可以退款
    param agentId 如果是代理商退款需要判断是否为该代理商订单
    */
    public function doRefund($id, $statusArr = null, $agentId = 0)
    {
        $orderInfo = ($this->orderModelName)::where('pay_status', 1)->where('refund_status', 0)->find($id);
        if (!$orderInfo) {
            return $this->inValiReturn('订单不存在');
        }

        // 代理商判断
        if ($orderInfo->agent_id != $agentId) {
            return $this->inValiReturn('无法退款操作');
        }
        // 限制订单退款状态
        if ($statusArr) {
            if (!in_array($orderInfo->status, $statusArr)) {
                return $this->valiReturn('当前'.(PRODUCT_SERV_TYPE[$orderInfo->serv_type]['name']).'订单状态不支持退款');
            }
        }

        // 单独设置变量 方便后续  支持部分退款
        $refundAmount = $orderInfo->pay_amount;

        // 第三方支付退款 todo  constant 定义 后台支付 的支付方式
        $apiRefundRes = false;
        try {
            if ($orderInfo->pay_amount) {
                if (PAY_WEIXIN == $orderInfo->pay_type) {
                    $payApi = new Wxpay($orderInfo->agent_id);
                    $data = [
                        'transactionId' => $orderInfo->out_order_id,
                        'refundNumber' => $orderInfo->id,
                        'totalFee' => $orderInfo->pay_amount,
                        'refundFee' => $refundAmount,
                        'desc' => '充值失败退款',
                    ];
                    $res = $payApi->refund($data);
                    if (!isset($res['result_code']) || 'SUCCESS' != $res['result_code']) {
                        $apiRefundRes = false;
                    } else {
                        $apiRefundRes = true;
                    }
                } elseif (PAY_ALIPAY == $orderInfo->pay_type) {
                    $payApi = new Alipay($orderInfo->agent_id);
                    $data = [
                        'out_trade_no' => $orderInfo->out_order_id,
                        'refund_amount' => $refundAmount,
                        'refund_reason' => PRODUCT_SERV_TYPE[$orderInfo->serv_type]['name'].'后台退款',
                    ];
                    $res = $payApi->refund($data)->toArray();
                    doLog('订单'.$id.'退款返回'.json_encode($res, JSON_UNESCAPED_UNICODE));
                    if (!isset($res['code']) || '10000' != $res['code'] || empty($res['refund_fee'])) {
                        $apiRefundRes = false;
                    } else {
                        $apiRefundRes = true;
                    }
                } elseif (3 == $orderInfo->pay_type) {
                    $apiRefundRes = true;
                } else {
                    return $this->inValiReturn('未知支付方式，请联系我们');
                }
            } else {
                //todo 没有实际支付的退款
                $apiRefundRes = true;
            }
        } catch (\Exception $e) {
            doWarning('退款api退款异常，订单id'.$id.',异常信息：'.getExceptionMsg($e));
        }

        if (!$apiRefundRes) {
            return $this->inValiReturn('发起第三方退款失败，请重试或者联系我们');
        }

        // 处理订单退款成功后的逻辑
        Db::startTrans();
        try {
            // 订单退款业务逻辑
            $res = $this->afterRefundApi($orderInfo, $refundAmount);
            if ($res['code']) {
                Db::rollback();

                return $res;
            }
            DB::commit();
            // todo step 是否需要做推送消
            return $this->valiReturn();
        } catch (\Exception $e) {
            DB::rollBack();
            doWarning('退款api退款完成后，业务流程处理异常，订单id'.$id.',异常信息：'.getExceptionMsg($e));

            return $this->inValiReturn('退款失败，请重试或者联系我们');
        }
    }

    /*
     退款api 执行后的退款流程
     orderInfo  Order::find 的数据
     refundAmount 退款金额 ，单位 分
    */
    private function afterRefundApi($orderInfo, $refundAmount)
    {
        // step 根据结算方式，处理用户账户余额以及资金明细记录
        if (SETTLE_TYPE_PREPAY == $orderInfo->agent_settle_type) {
            $agentInfo = Agent::find($orderInfo->agent_id);
            if (!$agentInfo) {
                return $this->inValiReturn('代理不存在');
            }
            // 还原代理商余额
            $res = Agent::where('id', $orderInfo->agent_id)->inc('balance', $refundAmount);
            if (!$res) {
                return $this->inValiReturn('退款失败，代理余额更新失败');
            }
            // 获取业务退款对应的balance type取值
            $balanceLogModel = new AgentBalanceLog();
            $balanceType = $balanceLogModel->servTypeRefund($orderInfo->serv_type);
            $balanceLog = [
               'agent_id' => $agentInfo->id,
               'type' => $balanceType,
               'amount' => $refundAmount,
               'old_balance' => $agentInfo->balance,
               'new_balance' => ($agentInfo->balance + $refundAmount),
               'remark' => PRODUCT_SERV_TYPE[$orderInfo->serv_type]['name'].'后台退款：'.fen2yuan($refundAmount).'元，订单号：'.$orderInfo->sn,
               'quote_id' => $orderInfo->id,
               'trade_no' => $orderInfo->out_order_id,
            ];

            if (!$balanceLogModel->save($balanceLog)) {
                return $this->inValiReturn('退款失败，资金变动明细记录失败');
            }
        }

        // step 根据订单商品信息 处理 供应商方面的退款 ，这里可以不管status 取值状态直接发送，但是需要根据返回值做判断
        if ($this->orderGoodsModelName) {
            //todo 推送供应，并结合实际 是否需要更新订单表的 status
        }

        // step 更新订单退款状态
        $res = ($this->orderModelName)::where('id', $orderInfo->id)
                ->where('pay_status', 1)
                ->where('refund_status', 0) // todo 是否还需要对 status 做检验
                ->update(['refund_status' => 1, 'refund_at' => date('Y-m-d H:i:s'), 'refund_amount' => $refundAmount]);
        if (!$res) {
            return $this->inValiReturn('退款更新订单失败，请重试或者联系我们');
        }

        // step 对账数据实时更新
        $res = (new DailyFinance())->decrease($orderInfo->agent_id, $orderInfo->refund_at, $orderInfo->amount, $orderInfo->agent_profit, $orderInfo->profit);
        if ($res['code']) {
            return $this->inValiReturn($res['msg']);
        }

        return $this->valiReturn();
    }
}
